#include "UnityPrefix.h"
#if ENABLE_SCRIPTING
#include "MonoScript.h"
#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
#include "Runtime/Serialize/TransferUtility.h"
#include "MonoManager.h"
#include "Runtime/Scripting/Backend/ScriptingBackendApi.h"
#include "MonoScriptCache.h"
#include "MonoBehaviour.h"

#if UNITY_FLASH || UNITY_WINRT
#include "Runtime/Scripting/Backend/ScriptingTypeRegistry.h"
#endif

const char* kBuildLogicGraphDllFile = "Temp/StagingArea/Data/Managed/LogicGraphs.dll";

string GetGeneratedLogicGraphsDllName()
{
	return GetLastPathNameComponent(kBuildLogicGraphDllFile);
}


using namespace std;


#if UNITY_EDITOR
static UInt32 ComputeTypeTreeHashForScriptClass (MonoScript* script)
{
	if (script->GetClass () == NULL
		|| !IsValidScriptType (script->GetScriptType ())
		|| script->IsEditorScript ())
		return 0;

	// We don't want to call the constructor as that may run user code and
	// may have all kinds of side-effects.  All we need is an instance for
	// running the TypeTree generation/serialization code on.
	ScriptingObjectPtr instance = scripting_object_new (script->GetClass ());
	if (!instance)
		return 0;

	// Create a temporary MonoBehaviour that we can run ProxyTransfer on.
	MonoBehaviour* dummyInstance = NEW_OBJECT (MonoBehaviour);
	dummyInstance->Reset ();
	dummyInstance->HackSetAwakeWasCalled ();

	// Connect MonoBehaviour to script class and to the object instance
	// we have created.
	dummyInstance->SetScript (script, instance);

	// Generate type tree.
	TypeTree typeTree;
	GenerateTypeTree (*dummyInstance, &typeTree);
	DestroySingleObject (dummyInstance);

	// Compute hash of tree.
	return HashTypeTree (typeTree);
}
#endif // UNITY_EDITOR


MonoScript::MonoScript (MemLabelId label, ObjectCreationMode mode)
:	Super(label, mode)
{
	m_PropertiesHash = 0;
	
	m_ScriptCache = NULL;
	m_IsEditorScript = false;
	m_ExecutionOrder = 0;
}

MonoScript::~MonoScript ()
{
	CleanupScriptCache();
}

UInt32 MonoScript::GetPropertiesHash ()
{
	// In the editor, compute the property hash on demand based on the type
	// tree generated by the MonoBehaviour serialization code.
	#if UNITY_EDITOR
	if (m_PropertiesHash == 0)
		m_PropertiesHash = ComputeTypeTreeHashForScriptClass (this);
	#endif

	return m_PropertiesHash;
}

UnityStr MonoScript::GetScriptFullClassName () const
{
	if (m_Namespace.empty())
		return m_ClassName;
	else
		return m_Namespace + "." + m_ClassName;
}

ScriptingClassPtr MonoScript::GetClass ()
{
	if (m_ScriptCache)
		return m_ScriptCache->klass;
	else
		return NULL;
}

ScriptingMethodPtr MonoScript::FindMethod (const char* name)
{
	if (m_ScriptCache)
		return ::FindMethod(*m_ScriptCache, name);
	else	
		return NULL;
}

MonoScriptType MonoScript::GetScriptType() const
{
	if (m_ScriptCache == NULL)
		return kScriptTypeNotInitialized;
	else
		return m_ScriptCache->scriptType;
}


void MonoScript::CleanupScriptCache ()
{
	if (m_ScriptCache != NULL)
	{
		const_cast<MonoScriptCache*> (m_ScriptCache)->Release();
		m_ScriptCache = NULL;
	}
}

#if ENABLE_SCRIPTING

void MonoScript::Rebuild (ScriptingTypePtr klass)
{
	CleanupScriptCache ();
	
	#if UNITY_EDITOR
	m_PropertiesHash = 0;
	#endif
	
	m_ScriptCache = CreateMonoScriptCache (klass, m_IsEditorScript, this);
}
#endif

void MonoScript::RebuildFromAwake()
{
#if ENABLE_MONO
	Rebuild (GetMonoManager().GetMonoClassWithAssemblyName (GetScriptClassName (), GetNameSpace(), GetAssemblyName ()));
#elif UNITY_FLASH || UNITY_WINRT
	ScriptingTypePtr t = GetScriptingTypeRegistry().GetType(GetNameSpace().c_str(), GetScriptClassName().c_str());
	Rebuild(t);
#endif
	
}

// Used by WinRT & Flash
UnityStr MonoScript::GetScriptName()
{
	UnityStr s = GetNameSpace();
	if (s.size() > 0)
		s += "." + GetScriptClassName();
	else
		s = GetScriptClassName();
	return s;
}

void MonoScript::AwakeFromLoad (AwakeFromLoadMode awakeMode)
{
	Super::AwakeFromLoad (awakeMode);
	if ((awakeMode & kDidLoadThreaded) == 0)
	{
		RebuildFromAwake();
	}
}

void MonoScript::AwakeFromLoadThreaded ()
{
	Super::AwakeFromLoadThreaded();
	RebuildFromAwake();
}

template<class TransferFunction>
void MonoScript::TransferPropertiesHash (TransferFunction& transfer)
{
	#if UNITY_EDITOR
	if (transfer.IsWritingGameReleaseData())
	{
		// Force building the properties hash
		GetPropertiesHash();
	}
	#endif
	
	// When reading or writing for game release
	// Transfer properties hash
	if (transfer.IsSerializingForGameRelease ())
		transfer.Transfer (m_PropertiesHash, "m_PropertiesHash", kNotEditableMask);	
}

#if UNITY_EDITOR
// Temporarily (When building the player during the write operation).
// Sets the correct assembly of the MonoScript, and restores it afterwards.
struct RemapAssemblyDuringBuild
{
	MonoScript* m_Script;
	string      m_AssemblyName;
	string      m_Namespace;
	string      m_ClassName;

	RemapAssemblyDuringBuild (MonoScript& inScript, bool logicGraphRelease)
		: m_Script (NULL)
	{
		if (!logicGraphRelease)
			return;
		
		m_Script = &inScript;
		
		m_AssemblyName = m_Script->GetAssemblyName();
		m_Namespace = m_Script->GetNameSpace();
		m_ClassName = m_Script->GetScriptClassName();

		m_Script->m_AssemblyName = GetGeneratedLogicGraphsDllName();
		m_Script->m_Namespace = "";
		
		m_Script->m_ClassName = m_Script->m_EditorGraphData->GetName();
	}
	
	~RemapAssemblyDuringBuild ()
	{
		if (m_Script == NULL)
			return;

		m_Script->m_AssemblyName = m_AssemblyName;
		m_Script->m_Namespace = m_Namespace;
		m_Script->m_ClassName = m_ClassName;
	}
};
#endif

template<class TransferFunction>
void MonoScript::Transfer (TransferFunction& transfer)
{
	// Don't transfer script class since we already transfer the script property ourselves.
	Super::Super::Transfer (transfer);
	
	transfer.SetVersion(4);
	
	#if UNITY_EDITOR

	bool logicGraphRelease = transfer.IsWritingGameReleaseData() && !m_EditorGraphData.IsNull();
	RemapAssemblyDuringBuild remap (*this, logicGraphRelease);

	if (!transfer.IsSerializingForGameRelease ())
	{
		transfer.Transfer (m_Script, "m_Script", kHideInEditorMask);
		transfer.Transfer (m_DefaultReferences, "m_DefaultReferences", kHideInEditorMask);
		transfer.Transfer (m_Icon, "m_Icon", kNoTransferFlags);
		TRANSFER (m_EditorGraphData);
	}

	#endif

	
	transfer.Transfer (m_ExecutionOrder, "m_ExecutionOrder", kNotEditableMask);
	TransferPropertiesHash (transfer);
	
	transfer.Transfer (m_ClassName, "m_ClassName", kNotEditableMask);
	transfer.Transfer (m_Namespace, "m_Namespace", kNotEditableMask);
	transfer.Transfer (m_AssemblyName, "m_AssemblyName", kNotEditableMask);
	transfer.Transfer (m_IsEditorScript, "m_IsEditorScript", kHideInEditorMask);

	// AssemblyIdentifier has been removed and is now simply the dll name
	if (transfer.IsVersionSmallerOrEqual(1))
	{
		transfer.Transfer (m_AssemblyName, "m_AssemblyIdentifier", kNotEditableMask);
		
		if (m_AssemblyName == "Unity Engine Special")
		{
			m_AssemblyName = "UnityEngine.dll";
			m_Namespace = "UnityEngine";
		}
		else if (m_AssemblyName == "Unity Editor Special")
		{
			m_AssemblyName = "UnityEditor.dll";
			m_Namespace = "UnityEditor";
		}
		else
		{
			m_AssemblyName = "Assembly - " + m_AssemblyName + ".dll";
		}
	}
	
	#if UNITY_EDITOR
	// In 2.x Assemblies compiled by Unity had spaces in the name.
	// In 3.0 we are removing all spaces, since this makes loading assemblies easier and fixes issue on xbox where the filename is too long.
	// ("Assembly - UnityScript.dll" -> "Assembly-UnityScript.dll") 
	if (transfer.IsVersionSmallerOrEqual(2))
	{
		if (BeginsWith(m_AssemblyName, "Assembly - "))
			replace_string(m_AssemblyName, " ", "");
	}
	#endif
}

bool MonoScript::ShouldIgnoreInGarbageDependencyTracking ()
{	
	// MonoScript can have referenced
	// But it's ok to ignore them eg. references to default references, icons and editor graph
	return true;
}

void MonoScript::Init (const ScriptString& script, const string& className, const std::string& nameSpace, const string& identifier, bool isEditorScript)
{
	SetScript(script);
	m_ClassName = className;
	m_Namespace = nameSpace;
	m_AssemblyName = identifier;
	m_PropertiesHash = 0;
	m_IsEditorScript = isEditorScript;
	
	SetDirty ();
}

#if ENABLE_SCRIPTING

void MonoScript::Init (MonoClass* scriptType)
{
	m_ClassName = scripting_class_get_name(scriptType);
	m_Namespace = scripting_class_get_namespace(scriptType);
	m_PropertiesHash = 0;
	#if ENABLE_MONO
	MonoImage* image = mono_class_get_image(scriptType);
	MonoAssemblyName name;
	bool success = mono_assembly_fill_assembly_name(image, &name);
	if (success)
		m_AssemblyName = mono_stringify_assembly_name(&name);
	#endif
	m_IsEditorScript = 0;
	SetDirty ();
}
#endif

std::string MonoScript::GetNameSpace ()const
{
	return m_Namespace;
}

#if ENABLE_SCRIPTING
MonoScript* CreateMonoScriptFromScriptingType(ScriptingTypePtr klass)
{
	MonoScript* result = NEW_OBJECT(MonoScript);
	result->Reset ();
	result->Init(klass);
	GetMonoScriptManager().RegisterRuntimeScript (*result);
	result->AwakeFromLoad(kInstantiateOrCreateFromCodeAwakeFromLoad);
	return result;
}
#endif

void MonoScript::SetExecutionOrder (SInt32 executionOrder)
{
	m_ExecutionOrder = executionOrder;
	SetDirty();
}

#if UNITY_EDITOR
bool MonoScript::IsBuiltinScript() const
{
	return m_AssemblyName == "UnityEngine.dll" || m_AssemblyName == "UnityEditor.dll";
}

void MonoScript::SetIcon (PPtr<Object> icon) 
{
	m_Icon = icon;
	// We do not call SetDirty() since MonoScript is generated data (MonoImporter holds the icon state)
}

PPtr<Object> MonoScript::GetIcon () const
{
	return m_Icon;
}

void MonoScript::SetEditorGraphData(Object* data)
{ 
	if (m_EditorGraphData != PPtr<Object> (data)) 
	{
		m_EditorGraphData = data;
		SetDirty();
	}
}

PPtr<Object> MonoScript::GetEditorGraphData()
{
	return m_EditorGraphData;
}

bool MonoScript::GetScriptTypeWasJustCreatedFromComponentMenu ()
{
	if (m_ScriptCache)
		return m_ScriptCache->scriptTypeWasJustCreatedFromComponentMenu;
	else
		return false;
}

void MonoScript::SetScriptTypeWasJustCreatedFromComponentMenu ()
{
	if (m_ScriptCache == NULL)
		Rebuild(NULL);
	
	const_cast<MonoScriptCache*> (m_ScriptCache)->scriptTypeWasJustCreatedFromComponentMenu = true;
}

std::string BuildScriptClassId(const std::string& assembly, const std::string& ns, const std::string& klass)
{
	return assembly + ":" + ns + ":" + klass;
}

void GetScriptClassIdComponents(const std::string& scriptClassId, std::string& assembly, std::string& ns, std::string& klass)
{
	std::vector<std::string> parts;
	Split(scriptClassId, ":", parts);
	
	assembly = parts[0];
	AssertIf(parts.size() != 2 && parts.size() != 3);
	// empty namespace
	if (parts.size() == 2)
	{
		ns.clear();
		klass = parts[1];
	}
	else if (parts.size() == 3)
	{
		ns = parts[1];
		klass = parts[2];
	}
}

#endif

#if ENABLE_SCRIPTING
IMPLEMENT_CLASS (MonoScript)
IMPLEMENT_OBJECT_SERIALIZE (MonoScript)
#endif

#endif
